using Math;

namespace Noise
{
    public static class Perlin
    {
        private static float Fade(float t)
        {
            return t*t*t*(t*(t*6-15)+10);
        }

        public static float Noise1(float x)
        {
            int i0, i1;
            float u;

            // Find unit line segment that contains point and find relative x of point in line segment
            Maths.GetIntegerAndFractional(x, out i0, out x);

            // Compute fade curves for x
            u = Fade(x);

            // Wrap integer coords at 255
            i1 = (i0+1)&255;
            i0 &= 255;

            // Hash coordinates and get the gradient at the 2 line segment end points
            float g0 = Gradient.Dot1(Permutation.Perm1(i0), x);
            float g1 = Gradient.Dot1(Permutation.Perm1(i1), x-1);

            // Blend the results along x
            float nx = Interpolate.Lerp(u, g0, g1);

            // Return the result, roughly normalised to the range [-1, 1]
            return nx;
        }

        public static float Noise1(float x, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float n = Noise1(x*freq);
                sum += amp*n;
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }

        public static float Noise2(float x, float y)
        {
            int i0, j0, i1, j1;
            float u, v;

            // Find unit square that contains point and find relative x,y of point in square
            Maths.GetIntegerAndFractional(x, out i0, out x);
            Maths.GetIntegerAndFractional(y, out j0, out y);

            // Compute fade curves for each of x,y
            u = Fade(x);
            v = Fade(y);

            // Wrap integer coords at 255
            i1 = (i0+1)&255;
            j1 = (j0+1)&255;
            i0 &= 255;
            j0 &= 255;

            // Hash coordinates and get the gradient at the 4 square corners
            float g00 = Gradient.Dot2(Permutation.Perm2(i0, j0), x, y);
            float g10 = Gradient.Dot2(Permutation.Perm2(i1, j0), x-1, y);
            float g01 = Gradient.Dot2(Permutation.Perm2(i0, j1), x, y-1);
            float g11 = Gradient.Dot2(Permutation.Perm2(i1, j1), x-1, y-1);

            // Blend the results along x
            float nx0 = Interpolate.Lerp(u, g00, g10);
            float nx1 = Interpolate.Lerp(u, g01, g11);

            // Blend the results along y
            float nxy = Interpolate.Lerp(v, nx0, nx1);

            // Return the result, roughly normalised to the range [-1, 1]
            return nxy;
        }

        public static float Noise2(float x, float y, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float n = Noise2(x*freq, y*freq);
                sum += amp*n;
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }

        public static float Noise3(float x, float y, float z)
        {
            int i0, j0, k0, i1, j1, k1;
            float u, v, w;

            // Find unit cube that contains point and find relative x,y,z of point in cube
            Maths.GetIntegerAndFractional(x, out i0, out x);
            Maths.GetIntegerAndFractional(y, out j0, out y);
            Maths.GetIntegerAndFractional(z, out k0, out z);

            // Compute fade curves for each of x,y,z
            u = Fade(x);
            v = Fade(y);
            w = Fade(z);

            // Wrap integer coords at 255
            i1 = (i0+1)&255;
            j1 = (j0+1)&255;
            k1 = (k0+1)&255;
            i0 &= 255;
            j0 &= 255;
            k0 &= 255;

            // Hash coordinates and get the gradient at the 8 cube corners
            float g000 = Gradient.Dot3(Permutation.Perm3(i0, j0, k0), x, y, z);
            float g100 = Gradient.Dot3(Permutation.Perm3(i1, j0, k0), x-1, y, z);
            float g010 = Gradient.Dot3(Permutation.Perm3(i0, j1, k0), x, y-1, z);
            float g110 = Gradient.Dot3(Permutation.Perm3(i1, j1, k0), x-1, y-1, z);
            float g001 = Gradient.Dot3(Permutation.Perm3(i0, j0, k1), x, y, z-1);
            float g101 = Gradient.Dot3(Permutation.Perm3(i1, j0, k1), x-1, y, z-1);
            float g011 = Gradient.Dot3(Permutation.Perm3(i0, j1, k1), x, y-1, z-1);
            float g111 = Gradient.Dot3(Permutation.Perm3(i1, j1, k1), x-1, y-1, z-1);

            // Blend the results along x
            float nx00 = Interpolate.Lerp(u, g000, g100);
            float nx10 = Interpolate.Lerp(u, g010, g110);
            float nx01 = Interpolate.Lerp(u, g001, g101);
            float nx11 = Interpolate.Lerp(u, g011, g111);

            // Blend the results along y
            float nxy0 = Interpolate.Lerp(v, nx00, nx10);
            float nxy1 = Interpolate.Lerp(v, nx01, nx11);

            // Blend the results along z
            float nxyz = Interpolate.Lerp(w, nxy0, nxy1);

            // Return the result, roughly normalised to the range [-1, 1]
            return nxyz;
        }

        public static float Noise3(float x, float y, float z, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float n = Noise3(x*freq, y*freq, z*freq);
                sum += amp*n;
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }
        
        public static float PNoise1(float x, int px)
        {
            int i0, i1;
            float u;

            // Find unit line segment that contains point and find relative x of point in line segment
            Maths.GetIntegerAndFractional(x, out i0, out x);

            // Compute fade curves for x
            u = Fade(x);

            // Wrap integer coords at 255, and px-1
            i1 = ((i0+1)%px)&255;
            i0 = (i0%px)&255;

            // Hash coordinates and get the gradient at the 2 line segment end points
            float g0 = Gradient.Dot1(Permutation.Perm1(i0), x);
            float g1 = Gradient.Dot1(Permutation.Perm1(i1), x-1);

            // Blend the results along x
            float nx = Interpolate.Lerp(u, g0, g1);

            // Return the result, roughly normalised to the range [-1, 1]
            return nx;
        }

        public static float PNoise1(float x, int px, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float n = PNoise1(x*freq, px);
                sum += amp*n;
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }

        public static float PNoise2(float x, float y, int px, int py)
        {
            int i0, j0, i1, j1;
            float u, v;

            // Find unit square that contains point and find relative x,y of point in square
            Maths.GetIntegerAndFractional(x, out i0, out x);
            Maths.GetIntegerAndFractional(y, out j0, out y);

            // Compute fade curves for each of x,y
            u = Fade(x);
            v = Fade(y);

            // Wrap integer coords at 255, and px-1, py-1
            i1 = ((i0+1)%px)&255;
            j1 = ((j0+1)%py)&255;
            i0 = (i0%px)&255;
            j0 = (j0%py)&255;

            // Hash coordinates and get the gradient at the 4 square corners
            float g00 = Gradient.Dot2(Permutation.Perm2(i0, j0), x, y);
            float g10 = Gradient.Dot2(Permutation.Perm2(i1, j0), x-1, y);
            float g01 = Gradient.Dot2(Permutation.Perm2(i0, j1), x, y-1);
            float g11 = Gradient.Dot2(Permutation.Perm2(i1, j1), x-1, y-1);

            // Blend the results along x
            float nx0 = Interpolate.Lerp(u, g00, g10);
            float nx1 = Interpolate.Lerp(u, g01, g11);

            // Blend the results along y
            float nxy = Interpolate.Lerp(v, nx0, nx1);

            // Return the result, roughly normalised to the range [-1, 1]
            return nxy;
        }

        public static float PNoise2(float x, float y, int px, int py, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float n = PNoise2(x*freq, y*freq, px, py);
                sum += amp*n;
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }

        public static float PNoise3(float x, float y, float z, int px, int py, int pz)
        {
            int i0, j0, k0, i1, j1, k1;
            float u, v, w;

            // Find unit cube that contains point and find relative x,y,z of point in cube
            Maths.GetIntegerAndFractional(x, out i0, out x);
            Maths.GetIntegerAndFractional(y, out j0, out y);
            Maths.GetIntegerAndFractional(z, out k0, out z);

            // Compute fade curves for each of x,y,z
            u = Fade(x);
            v = Fade(y);
            w = Fade(z);

            // Wrap integer coords at 255, and px-1, py-1, pz-1
            i1 = ((i0+1)%px)&255;
            j1 = ((j0+1)%py)&255;
            k1 = ((k0+1)%pz)&255;
            i0 = (i0%px)&255;
            j0 = (j0%py)&255;
            k0 = (k0%pz)&255;

            // Hash coordinates and get the gradient at the 8 cube corners
            float g000 = Gradient.Dot3(Permutation.Perm3(i0, j0, k0), x, y, z);
            float g100 = Gradient.Dot3(Permutation.Perm3(i1, j0, k0), x-1, y, z);
            float g010 = Gradient.Dot3(Permutation.Perm3(i0, j1, k0), x, y-1, z);
            float g110 = Gradient.Dot3(Permutation.Perm3(i1, j1, k0), x-1, y-1, z);
            float g001 = Gradient.Dot3(Permutation.Perm3(i0, j0, k1), x, y, z-1);
            float g101 = Gradient.Dot3(Permutation.Perm3(i1, j0, k1), x-1, y, z-1);
            float g011 = Gradient.Dot3(Permutation.Perm3(i0, j1, k1), x, y-1, z-1);
            float g111 = Gradient.Dot3(Permutation.Perm3(i1, j1, k1), x-1, y-1, z-1);

            // Blend the results along x
            float nx00 = Interpolate.Lerp(u, g000, g100);
            float nx10 = Interpolate.Lerp(u, g010, g110);
            float nx01 = Interpolate.Lerp(u, g001, g101);
            float nx11 = Interpolate.Lerp(u, g011, g111);

            // Blend the results along y
            float nxy0 = Interpolate.Lerp(v, nx00, nx10);
            float nxy1 = Interpolate.Lerp(v, nx01, nx11);

            // Blend the results along z
            float nxyz = Interpolate.Lerp(w, nxy0, nxy1);

            // Return the result, roughly normalised to the range [-1, 1]
            return nxyz;
        }

        public static float PNoise3(float x, float y, float z, int px, int py, int pz, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float n = PNoise3(x*freq, y*freq, z*freq, px, py, pz);
                sum += amp*n;
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }

        public static float Billowed2(float x, float y)
        {
            return Maths.Abs(Noise2(x, y));
        }

        public static float Billowed2(float x, float y, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float n = Billowed2(x*freq, y*freq);
                sum += amp*n;
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }

        public static float Ridged2(float x, float y)
        {
            return 1.0f-Maths.Abs(Noise2(x, y));
        }

        public static float Ridged2(float x, float y, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float n = Ridged2(x*freq, y*freq);
                sum += amp*n;
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }
    }
}